/*
 * Course:    CS 590 Web Services
 * Semseter:  Spring 2009
 * Professor: Sedlemeyer
 * Project:   CHEAT (Code Help Educational Assignment Tool)
 * Due Date:  May 1, 2009
 * Team:      Sarcasm
 *            (Baker, Jason; Mitchell, Robin; Shie, Keith)
 */
package cheat.administration;


import cheat.entities.AccessType;
import cheat.entities.ArchiveType;
import cheat.entities.AssignmentKeywordType;
import cheat.entities.AssignmentType;
import cheat.entities.IsoLanguage;
import cheat.entities.Person;
import cheat.entities.RegisteredUser;
import cheat.entities.StatusType;
import java.util.List;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import javax.xml.ws.WebServiceRef;
import org.oorsprong.websamples.ArrayOftLanguage;
import org.oorsprong.websamples.CountryInfoService;
import org.oorsprong.websamples.CountryInfoServiceSoapType;
import org.oorsprong.websamples.TLanguage;


/**
 * Provides the functionality related to
 * populate the operational data required to support the assignment sharing system.
 *
 * <p>Values for the following data/entities are populated:
 * <ul>
 * <li>admin user:<ul>
 * <li>{@linkplain cheat.entities.Person Person}</li>
 * <li>{@linkplain cheat.entities.RegisteredUser RegisteredUser}</li></ul></li>
 * <li>Reference data:<ul>
 * <li>{@linkplain cheat.entities.AccessType AccessType}</li>
 * <li>{@linkplain cheat.entities.AssignmentKeywordType AssignmentKeywordType}</li>
 * <li>{@linkplain cheat.entities.AssignmentType AssignmentType}</li>
 * <li>{@linkplain cheat.entities.IsoLanguage IsoLanguage}</li>
 * <li>{@linkplain cheat.entities.StatusType StatusType}</li>
 * </ul></li>
 * </ul</p>
 *
 * <p>The values to populate the IsoLanguage entity are obtained from a
 * <a href="http://www.oorsprong.org/websamples.countryinfo/CountryInfoService.wso">web service</a>.</p>
 *
 * @author Keith Shie
 */
@Stateless
public class PopulationManagementBean implements PopulationManagementRemote, PopulationManagementLocal {

  @WebServiceRef(
  wsdlLocation =
  "META-INF/wsdl/client/CountryInfoService/www.oorsprong.org/websamples.countryinfo/CountryInfoService.wso.wsdl" )
  private CountryInfoService service;
  @PersistenceUnit( unitName = "CheatApp-ejbPU" )
  private EntityManagerFactory emf;
  @EJB
  private PopulationManagementLocal populationManagement;
  final static private String ADMIN_ACCESS_CD = "ADMIN";
  private static Logger logger = Logger.getLogger( PopulationManagementBean.class.getPackage().getName() );


  /**
   * Populate the {@linkplain cheat.entities.AccessType AccessType} operational entities.
   *
   * <p>Ideally, this method would not be part of a public API.  However, we
   * need to gain control over the transaction so that we can force the
   * persistance layer to write these entities into the database so they can be referenced
   * by other entities.  Providing this method as part of the public API is the most
   * straight forward way to gain this control.</p>
   *
   */
  @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW )
  public void populateAccessTypes() {
    final EntityManager em = emf.createEntityManager();

    logger.finer( "populateAccessTypes: start" );

    for ( AccessType at : new AccessType[]{ new AccessType( AccessType.ADMIN_CODE, "Administrator" ),
                                            new AccessType( AccessType.FACULTY_CODE, "Instructor" ),
                                            new AccessType( AccessType.STUDENT_CODE, "Student" ) } ) {
      final AccessType f = em.find( AccessType.class, at.getAccessCode() );
      if ( f != null ) {
        f.setDescription( at.getDescription() );
        em.persist( f );
      } else {
        em.persist( at );
      }
    }
    logger.finer( "populateAccessTypes: end" );
  }


  /**
   * Populate the {@linkplain cheat.entities.Person Person} operational entities.
   *
   * <p>Ideally, this method would not be part of a public API.  However, we
   * need to gain control over the transaction so that we can force the
   * persistance layer to generate the PK for the underlying License
   * Entity.  Providing this method as part of the public API is the most
   * straight forward way to gain this control.</p>
   *
   * @return the Person entity that was created.
   */
  @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW )
  public Person populateAdminPerson() {
    final EntityManager em = emf.createEntityManager();

    logger.finer( "populateAdminPerson: start" );

    Person p = new Person();

    p.setEmail( "sarcasm@sarcasm.org" );
    p.setAffiliation( "The Center for Sarcastic Excellence" );
    p.setLastName( "sarcasm" );
    p.setFirstName( "I am" );

    final Query existingPersonQuery = em.createNamedQuery( "Person.findByEmail" );
    existingPersonQuery.setParameter( "email", p.getEmail() );


    Person existingPerson = null;
    try {
      existingPerson = (Person) existingPersonQuery.getSingleResult();
    } catch ( Exception e ) {
    }

    if ( existingPerson != null ) {
      existingPerson.setAffiliation( p.getAffiliation() );
      existingPerson.setLastName( p.getLastName() );
      existingPerson.setFirstName( p.getFirstName() );

      em.persist( existingPerson );
      p = existingPerson;
    } else {
      em.persist( p );
    }

    logger.finer( "populateAdminPerson: end" );

    return p;
  }


  private void populateAdminUser( EntityManager em ) {

    /* We must first save the Person within their our transaction so that
    we can get the Person's generated id, fortunately, we can accoplish this through
    the local interface to this Session */
    Person p = populationManagement.populateAdminPerson();

    final RegisteredUser ru = new RegisteredUser();

    ru.setPerson( p );
    ru.setUserId( "sarcasm" );
    ru.setPassword( "sarcasm" );

    final Query existingAccessQuery = em.createNamedQuery( "AccessType.findByAccessCode" );
    existingAccessQuery.setParameter( "accessCode", ADMIN_ACCESS_CD );
    AccessType access = null;
    try {
      access = (AccessType) existingAccessQuery.getSingleResult();
      ru.setAccessCode( access );
    } catch ( NoResultException e ) {
      // As long as the operational data is in place, this shouldn't happen
      System.out.println( "Can't find " + ADMIN_ACCESS_CD );
    }

    final Query existingStatusQuery = em.createNamedQuery( "StatusType.findByStatcd" );
    existingStatusQuery.setParameter( "statCode", "ACT" );
    StatusType status = null;
    try {
      status = (StatusType) existingStatusQuery.getSingleResult();
      ru.setStatCode( status );
    } catch ( NoResultException e ) {
      // As long as the operational data is in place, this shouldn't happen
    }

    final Query existingUserQuery = em.createNamedQuery( "RegisteredUser.findByUserId" );
    existingUserQuery.setParameter( "userId", ru.getUserId() );
    RegisteredUser existingUser = null;
    try {
      existingUser = (RegisteredUser) existingUserQuery.getSingleResult();
    } catch ( Exception e ) {
    }

    if ( existingUser != null ) {
      existingUser.setAccessCode( ru.getAccessCode() );
      existingUser.setStatCode( ru.getStatCode() );
      existingUser.setPassword( ru.getPassword() );
      existingUser.setPerson( ru.getPerson() );

      em.persist( existingUser );
    } else {
      em.persist( ru );
    }
  }


  private void populateArchiveType( final EntityManager em ) {
    /* Save the Assignment Types */
    for ( ArchiveType at : new ArchiveType[]{ new ArchiveType( ArchiveType.ZIP_ARCHIVE_TYPE, "Zip" ),
                                              new ArchiveType( "gzip", "GNU Zip" ),
                                              new ArchiveType( "tar", "UNIX Tape Archive" ),
                                              new ArchiveType( "bz2", "BZip2" ),
                                              new ArchiveType( "lz", "lzip" ),
                                              new ArchiveType( "arc", "arc" ),
                                              new ArchiveType( "cab", "Cabinet" ),
                                              new ArchiveType( "lha", "lha" ),
                                              new ArchiveType( "7z", "7-zip" ) } ) {
      final ArchiveType f = em.find( ArchiveType.class, at.getName() );
      if ( f != null ) {
        f.setDescription( at.getDescription() );
        em.persist( f );
      } else {
        em.persist( at );
      }
    }
  }


  private void populateAssignmentContentType( final EntityManager em ) {
    /* Save the Assignment Types */
    for ( AssignmentKeywordType at : new AssignmentKeywordType[]{ new AssignmentKeywordType( "TPC", "Topic" ),
                                                                  new AssignmentKeywordType( "PRERQ", "Prerequisite" ),
                                                                  new AssignmentKeywordType( "OTH", "Other" ) } ) {
      final AssignmentKeywordType f = em.find( AssignmentKeywordType.class, at.getKeywordCode() );
      if ( f != null ) {
        f.setDescription( at.getDescription() );
        em.persist( f );
      } else {
        em.persist( at );
      }
    }
  }


  private void populateAssignmentTypes( final EntityManager em ) {
    /* Save the Assignment Types */
    for ( AssignmentType at : new AssignmentType[]{ new AssignmentType( "program", "Program" ),
                                                    new AssignmentType( "class", "Class" ),
                                                    new AssignmentType( "method", "Method" ),
                                                    new AssignmentType( "function", "Function" ),
                                                    new AssignmentType( "procedure", "Procedure" ),
                                                    new AssignmentType( "block", "Block" ),
                                                    new AssignmentType( "statement", "Statement" ),
                                                    new AssignmentType( "declaration", "Declaration" ),
                                                    new AssignmentType( "other", "Other" ) } ) {
      final AssignmentType f = em.find( AssignmentType.class, at.getAssignmentTypeCode() );
      if ( f != null ) {
        f.setDescription( at.getDescription() );
        em.persist( f );
      } else {
        em.persist( at );
      }
    }
  }


  private void populateIsoLanguageCodes( final EntityManager em ) {
    /* Save the ISO language codes */
    try {
      // Call Web Service to obtain ISO 639-2 (alpha-3) list of language codes
      CountryInfoServiceSoapType port = service.getCountryInfoServiceSoap();
      // TODO process result here
      final ArrayOftLanguage result = port.listOfLanguagesByCode();
      if ( result != null ) {
        final List<TLanguage> isoCodeList = result.getTLanguage();
        for ( TLanguage cn : isoCodeList ) {
          final IsoLanguage f = em.find( IsoLanguage.class, cn.getSISOCode() );
          if ( f != null ) {
            f.setLanguageDescription( cn.getSName() );
            em.persist( f );
          } else {
            em.persist( new IsoLanguage( cn.getSISOCode(), cn.getSName() ) );
          }
        }
      }
    } catch ( Exception ex ) {
      // TODO handle custom exceptions here
    }
  }


  /**
   * Populate the {@linkplain cheat.entities.StatusType StatusType} operational entities.
   *
   * <p>Ideally, this method would not be part of a public API.  However, we
   * need to gain control over the transaction so that we can force the
   * persistance layer to write these entities into the database so they can be referenced
   * by other entities.  Providing this method as part of the public API is the most
   * straight forward way to gain this control.</p>
   *
   */
  @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW )
  public void populateStatusTypes() {
    final EntityManager em = emf.createEntityManager();

    logger.finer( "populateStatusTypes: start" );

    for ( StatusType st : new StatusType[]{ new StatusType( StatusType.ACTIVE_CD, "Active" ),
                                            new StatusType( StatusType.PENDING_CD, "Pending" ),
                                            new StatusType( StatusType.HISTORIC_CD, "Historical" ) } ) {
      final StatusType f = em.find( StatusType.class, st.getStatCode() );
      if ( f != null ) {
        f.setDescription( st.getDescription() );
        em.persist( f );
      } else {
        em.persist( st );
      }
    }
    logger.finer( "populateStatusTypes: end" );
  }


  /**
   * Populate the operational entities required by the assignment sharing system.
   *
   * <p>Values for the following data/entities are populated:
   * <ul>
   * <li>admin user:<ul>
   * <li>{@linkplain cheat.entities.Person Person}</li>
   * <li>{@linkplain cheat.entities.RegisteredUser RegisteredUser}</li></ul></li>
   * <li>Reference data:<ul>
   * <li>{@linkplain cheat.entities.AccessType AccessType}</li>
   * <li>{@linkplain cheat.entities.AssignmentKeywordType AssignmentKeywordType}</li>
   * <li>{@linkplain cheat.entities.AssignmentType AssignmentType}</li>
   * <li>{@linkplain cheat.entities.IsoLanguage IsoLanguage}</li>
   * <li>{@linkplain cheat.entities.StatusType StatusType}</li>
   * </ul></li>
   * </ul</p>
   *
   */
  @TransactionAttribute( TransactionAttributeType.REQUIRED )
  public void populateOperationalData() {
    final EntityManager em = emf.createEntityManager();

    logger.finer( "populateOperationalData: start" );

    populationManagement.populateStatusTypes();
    populateAssignmentTypes( em );
    populateAssignmentContentType( em );
    populateArchiveType( em );
    populationManagement.populateAccessTypes();
    populateIsoLanguageCodes( em );

    populateAdminUser( em );

    logger.finer( "populateOperationalData: end" );
  }
}
